﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Kokomo.Mirror.CliMetadata
{
	public class BitReader
	{
		private byte[] _data;
		public byte[] Data { get { return this._data; } }

		private int _position;
		public int Position
		{
			get { return this._position; }
			set { this._position = value; }
		}

		public BitReader(byte[] data)
		{
			if (data == null) throw new ArgumentNullException("data");

			this._data = data;
			this._position = 0;
		}

		public bool IsEndOfStream { get { return this._position >= this._data.Length; } }

		public byte ReadByte()
		{
			return this._data[this._position++];
		}
		public byte PeekByte()
		{
			return this._data[this._position];
		}

		#region Numerics
		public sbyte ReadSByte()
		{
			return (sbyte)this._data[this._position++];
		}
		public short ReadInt16()
		{
			short value = BitConverter.ToInt16(this._data, this._position);
			this._position += 2;
			return value;
		}
		public ushort ReadUInt16()
		{
			ushort value = BitConverter.ToUInt16(this._data, this._position);
			this._position += 2;
			return value;
		}
		public int ReadInt32()
		{
			int value = BitConverter.ToInt32(this._data, this._position);
			this._position += 4;
			return value;
		}
		public uint ReadUInt32()
		{
			uint value = BitConverter.ToUInt32(this._data, this._position);
			this._position += 4;
			return value;
		}
		public long ReadInt64()
		{
			long value = BitConverter.ToInt64(this._data, this._position);
			this._position += 8;
			return value;
		}
		public ulong ReadUInt64()
		{
			ulong value = BitConverter.ToUInt64(this._data, this._position);
			this._position += 8;
			return value;
		}
		public float ReadSingle()
		{
			float value = BitConverter.ToSingle(this._data, this._position);
			this._position += 4;
			return value;
		}
		public double ReadDouble()
		{
			double value = BitConverter.ToDouble(this._data, this._position);
			this._position += 8;
			return value;
		}
		#endregion
		#region Other primitives
		public char ReadUtf16Char()
		{
			return (char)this.ReadUInt16();
		}
		public bool ReadBoolean()
		{
			return (this.ReadByte() != 0);
		}
		#endregion

		#region Attribute-related
		public string ReadSerString()
		{
			int length = this.ReadCompressedInt();
			if (length == -1) return null;

			//byte[] stringData = reader.ReadBytes(length);
			string str = Encoding.UTF8.GetString(this._data, this._position, length);

			this._position += length;

			return str;
		}
		public int ReadCompressedInt()
		{
			int num = this._data[this._position++];
			if (num == 0xFF) return -1;
			if ((num & 0x80) == 0)
				return num;	//num = leadingByte;
			else if ((num & 0xC0) == 0x80)
			{
				num =
					((num & 0x3F) << 8)
					| this._data[this._position++]
					;
			}
			else if ((num & 0xE0) == 0xC0)
			{
				num =
					((num & 0x10) << 24)
					| (this._data[this._position++] << 16)
					| (this._data[this._position++] << 8)
					| this._data[this._position++]
					;
			}
			else
			{
				throw new ArgumentNullException("Invalid integer encoding");
			}

			return num;
		}

		#endregion
	}
}
